package intopo.evogame.partnerselection.population;

import intopo.evogame.partnerselection.Cache;
import intopo.evogame.partnerselection.Database;
import intopo.evogame.partnerselection.config.ConstructStrategyException;
import intopo.evogame.partnerselection.config.ParsingException;
import intopo.evogame.partnerselection.config.TagException;
import intopo.evogame.partnerselection.chromosome.Chromosome;
import intopo.evogame.partnerselection.chromosome.ChromosomeFactory;
import intopo.evogame.partnerselection.chromosome.ChromosomeGenerator;
import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import intopo.evogame.partnerselection.player.AbstractPlayer;
import intopo.evogame.partnerselection.player.PlayerFactory;
import intopo.game.Strategy;
import intopo.util.Util;

import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;

/**
 *
 * <h3> Config Text File </h3>
 * <p> A node can be constructed from information stored in the configuration file.
 * This information is stored in a node section.  Each section starts with information that represents the players in the node and finishes with node properties.
 * The format of the first part is specified by class {@code PlayerCollection}.  This part begins with an integer that represents the
 * number of player entries.  Each player entry  has node sections where
 * <pre>
 * node_section ::- player_entries node_properties
 * player_entries_part ::- INT player_entries
 * player_entries ::- player_entry player_entries
 * player_entries ::-
 * player_entry ::- INT chromosome strategy
 * </pre>
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0 __DATE__
 */
public class Node<
		S extends Strategy<S>,
		C extends Chromosome<S>,
		P extends AbstractPlayer<S, C, P>>
{
	/**
	 * Node identification.
	 */
	final public int ID;
	/**
	 * Node carrying capacity.  Players in a node can die from overcrowing.  The death probability follows a sigmoid function.  The sigmoid function {@code sig(x)} that is used has the following properties:
	 *
	 * <ul>
	 * <li> {@code sig(carryingCapacity) = 0.5} - whenever there are {@code carryingCapacity} players in this node, there is 50% chance of a player dying of overcrowind;</li>
	 *
	 * <li> </li>
	 * </ul>
	 */
	final private int carryingCapacity;
	/**
	 * Players in this node.
	 */
	final PlayerCollection<S, C, P> players;
	/**
	 * Neighbouring nodes of this one.  Born players can be placed in the node's parent player or in one neighbour node.
	 */
	final private LinkedList<Node> neighbourNodes;
	/**
	 * 
	 */
	final private PlayerFactory<S, C, P> playerFactory;
	/**
	 * Generator used to assign identifications to nodes.
	 *
	 * <p> <b>Warning</b> Usage of this field is not synchronised.
	 */
	static private int idGenerator = 0;
	/**
	 * This list holds the offspring when calculating the next generation.  All players in this that are capable of reproducing, have their offspring stored in this list.  The players in this list must have their set of candidate partners correctly initialised.
	 */
	transient final private LinkedList<CP> nursery = new LinkedList<CP> ();
	class CP
	{
		final C chromosome;
		final int parentID;
		P offspring;
		CP (C chromosome, P parent)
		{
			this.chromosome = chromosome;
			this.parentID = parent.getID ();
		}
	}
	/**
	 * This list holds the corpses when calculating the next generation.  All players that die must be removed from the remaining players set of candidate partners.
	 */
	transient final private LinkedList<P> cemetery = new LinkedList<P> ();
	/**
	 * Values of the sigmoid function used to calculate if a player dies of overcrowding or not.
	 * @see Node#carryingCapacity
	 */
	transient final float[] sigmoid;
//	/**
//	 * Construct a node with the data stored in the generator.
//	 * <p> The list of neighbouring nodes is constructed but not filled.  Only when all nodes have been constructed we can fill this structure.
//	 * @param carryingCapacity  The carrying capacity of this node.
//	 * @param generator  The chromosome generator used to create the initial population in this node.
//	 * @param factory  The player factory used to create the initial population in this node.
//	 */
//	Node (int carryingCapacity, ChromosomeGenerator<S, C> generator, PlayerFactory<S, C, P> factory)
//		throws IOException
//	{
//		this.ID = Node.idGenerator++;
//		this.carryingCapacity = carryingCapacity;
//		this.neighbourNodes = new LinkedList<Node> ();
//		this.playerFactory = factory;
//		this.sigmoid = logitFunction (this.carryingCapacity);
//		// write this node to the database
//		this.writeStream ();
//	}
	/**
	 * Construct a node with the data available in the scanner.
	 * <p> The list of neighbouring nodes is constructed but not filled.  Only when all nodes have been constructed we can fill this structure.
	 * @param scanner  The
	 * @param playerFactory
	 * @param chromosomeFactory
	 * @param map
	 * @param defaultGenes
	 * @throws java.lang.InstantiationException
	 * @throws java.lang.IllegalAccessException
	 * @throws java.lang.IllegalArgumentException
	 * @throws java.lang.reflect.InvocationTargetException
	 * @throws java.io.IOException
	 */
	Node (Scanner scanner, PlayerFactory<S, C, P> playerFactory, ChromosomeFactory<S, C> chromosomeFactory, Map<String, Constructor<? extends S>> map)
		throws
			IOException,
			TagException,
			ConstructStrategyException,
			ParsingException
	{
		this.ID = Node.idGenerator++;
		this.players = new PlayerCollection<S, C, P> (scanner, playerFactory, chromosomeFactory, map, this);
		this.carryingCapacity = AbstractTextConfigFile.readInt (scanner, "node carrying capacity");
		this.neighbourNodes = new LinkedList<Node> ();
		this.playerFactory = playerFactory;
		this.sigmoid = logitFunction (this.carryingCapacity);
		// write this node to the database
		this.writeStream ();
	}
	/**
	 * Create a connection between this node and the given node.
	 * <p> Only when all nodes have been created, we initiliase the players set of candidate partners.
	 * @param node
	 */
	void link (Node<S, C, P> node)
	{
		this.neighbourNodes.add (node);
//		node.neighbourNodes.add (this);
	}
	/**
	 * After all nodes have been created and links between them have been established, we can initialise the set of candidate partners (or neighbours) of every player.
	 */
	void initPlayersNeighbours ()
	{
		for (P p : this.players) {
			for (P q : this.players) {
				if (p != q) {
					p.addNeighbour (q);
				}
			}
			for (Node<S, C, P> n : this.neighbourNodes) {
				for (P q : n.players) {
					p.addNeighbour (q);
				}
			}
		}
	}
	/**
	 * Calculates the next generation of players. Players that have reached the reproduction phase are replaced by their offspring.
	 * <p> This method is part of the process to calculate the next generation of players.
	 * @see GeographicPopulation#nextGeneration(int)
	 * @throws java.io.IOException If there is an error writing the chromosome to the database.
	 */
	void calcNurseryCemetry (int time)
		throws IOException
	{
		Iterator<P> iterator;
		// check who can reproduce and who dies
		iterator = this.players.iterator ();
		int size = this.players.size ();
		while (iterator.hasNext ()) {
			P p = iterator.next ();
			C child = p.reproduction ();
			if (child != null) {
				this.nursery.add (new CP (child, p));
			}
			//FIX carrying capacity
			if (p.dies () || deathOvercrowding (size)) {
				p.writePlayerDeath (time);
				this.cemetery.add (p);
				iterator.remove ();
//				size--;
			}
		}
	}
	/**
	 * Perform the death for overcrowding event.  The success probability follows a logit distribution.
	 * <p> We cache the values of the sigmoid function in field {@code sigmoid}.
	 * @param size  Population current size
	 * @return {@code true} if a player should die at the given population size.
	 * @see #sigmoid
	 */
	private boolean deathOvercrowding (int size)
	{
		return Cache.supply.nextFloat () < this.sigmoid [size];
	}
	/**
	 * Update the set of candidate partners because of dead players.
	 * <p> This method is part of the process to calculate the next generation of players.
	 * @return {@code true} if there are suficient players in this node to play a game.
	 * @see GeographicPopulation#nextGeneration(intopo.game.Game)
	 */
	boolean neighbourDied ()
	{
		// update neighbours of remaining players
		for (P pr : this.players) {
			for (P pd : this.cemetery) {
				pr.neighbourDied (pd);
			}
		}
		for (Node<S, C, P> n : this.neighbourNodes) {
			for (P pr : n.players) {
				for (P pd : this.cemetery) {
					pr.neighbourDied (pd);
				}
			}
		}
		return (this.players.size () + this.nursery.size () >= Cache.numberPlayers);
	}
	/**
	 * Places the players in list {@code nursery} into the <i>field</i>.
	 * <p> This method is part of the process to calculate the next generation of players.
	 * @see GeographicPopulation#nextGeneration(intopo.game.Game)
	 * @throws java.io.IOException If there is an error writing the player to the database.
	 */
	void placeOffspring (int time)
		throws IOException
	{
		for (CP pb : this.nursery) {
			Node<S, C, P> node;
			if (Cache.supply.nextFloat () < Node.Parameters.migrationProbability) {
				node = this.neighbourNodes.get (Util.nextPositiveInt (this.neighbourNodes.size ()));
			}
			else {
				node = this;
			}
			pb.offspring = this.playerFactory.create (pb.chromosome, pb.parentID, node, time);
			node.addPlayer (pb.offspring);
		}
	}
	/**
	 * Initialises and updates the set of candidate partners of new born players and players that remained alive, respectively,
	 * and update the population players that are alive in the next generation of the evolutionary algorithm.
	 *
	 * <p> This method is part of the process to calculate the next generation of players.
	 *
	 * @see GeographicPopulation#nextGeneration(intopo.game.Game)
	 * 
	 * @param players The players in the population
	 */
	void updatePlayerNeighboursPopulationPlayers (PlayerCollection<S, C, P> popPlayers)
	{
		// initialise probability, combination and weight vectors
		for (CP pb : this.nursery) {
			popPlayers.add (pb.offspring);
			pb.offspring.initProbabilityCombinationVectors ();
		}
		for (P pr : this.players) {
			pr.checkInvalidCombinations ();
		}
		for (P pd : this.cemetery) {
			popPlayers.remove (pd);
		}
		this.nursery.clear ();
		this.cemetery.clear ();
	}
	/**
	 * Adds a new born player in this node.  This methods add the given player to the set of candidate partners of all players in this node and neighbouring nodes.
	 * @param player
	 * @see AbstractPlayer#writePlayer(int, int)
	 */
	private void addPlayer (P player)
	{
		for (P p : this.players) {
			p.addNeighbour (player);
			player.addNeighbour (p);
		}
		for (Node<S, C, P> n : this.neighbourNodes) {
			for (P p : n.players) {
				p.addNeighbour (player);
				player.addNeighbour (p);
			}
		}
		this.players.add (player);
	}
	private void writeStream ()
		throws java.io.IOException
	{
		Database.singleton.node.writeInt (this.ID);
		Database.singleton.node.writeInt (this.carryingCapacity);
	}
	public void debug ()
	{
		System.out.format ("* ** Node %d ** *\n", this.ID);
//		System.out.print ("Logit function: [");
//		for (float f : this.sigmoid) {
//			System.out.print (f);
//			System.out.print (' ');
//		}
//		System.out.println ("\n");
		System.out.print ("neighbours : [");
		if (!this.neighbourNodes.isEmpty ()) {
			Iterator<Node> iterator = this.neighbourNodes.iterator ();
			System.out.print (iterator.next ().ID);
			while (iterator.hasNext ()) {
				System.out.format (" %d", iterator.next ().ID);
			}
		}
		System.out.println ("]");
		System.out.println ("players :");
		for (P p : this.players) {
			System.out.print ("         ");
			p.debug ();
			System.out.println ();
		}
	}
	/**
	 * Return the values of the logit function used to calculate the player death event.
	 *
	 * <p> The logit function used as the following characteristics:
	 * 
	 * <ul>
	 * <li> when there are exactly {@code carryingCapacity} players the probability of a player dying is {@code 0.5};
	 *
	 * <li> when there are {@code carryingCapacity/2} or less players, a player rarely dies;
	 *
	 * <li> from {@code carryingCapacity/2} to {@code 3*carryingCapacity/2} players the probability of a player steadly increases;
	 *
	 * <li> in a population of {@code 3*carryingCapacity/2} players death event is 
	 * @param carryingCapacity
	 * @return the values of the logit function used to calculate the player death event.
	 */
	static private float[] logitFunction (int carryingCapacity)
	{
		float[] result = new float [2 * carryingCapacity + 1];
		for (int i = result.length - 1; i >= 0; i--) {
			float value = 6f * (i - carryingCapacity) / carryingCapacity;
			float logitValue = 1f / (1f + (float) Math.exp (-value));
			result [i] = logitValue;
		}
		return result;
	}

	/**
	 *
	 */
	static public class Parameters
	{
		/**
		 * When a new born player is put in the population, there is some probability that it will migrate to a neighbouring node.
		 */
		static public float migrationProbability;
		/**
		 *
		 * @param scanner
		 */
		static public void read (Scanner scanner)
			throws ParsingException
		{
			Parameters.migrationProbability = AbstractTextConfigFile.readFloat (scanner, "Migration probability");
		}

		public static void write (PrintStream ps)
		{
			ps.print (Node.Parameters.migrationProbability);
			ps.println ("\t# migration probability");
		}
	}
}

// Local Variables:
// mode: java
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
